package solver;

import gurobi.GRB;
import gurobi.GRBEnv;
import gurobi.GRBException;
import gurobi.GRBLinExpr;
import gurobi.GRBModel;
import gurobi.GRBVar;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import model.BalanceObjective;
import model.EnhancedCostEvaluator;
import model.IncrementalCostCalculator;
import model.IncrementalValidator;
import model.Location;
import model.Machine;
import model.Neighborhood;
import model.Problem;
import model.Resource;
import model.Service;
import model.Solution;
import model.SolverEnvironment;

import command.IncrementalMoveProcessToMachineCommand;
import command.Invoker;

public class GrbLPModelBuilder {
	int P, M, R, L, S, N, BO;
	Problem problem;
	Solution solution;
	boolean verbose = true;
	SolverEnvironment solverenv;

	GRBVar[][] x;
	GRBVar[][] y;
	GRBVar[][] k;
	GRBVar[][] lo_cost; // load cost
	GRBVar[][][] a;
	GRBVar[][] abs;
	GRBVar[][] ba_cost; // balance cost
	GRBVar pm_cost; // process move cost
	GRBVar mm_cost; // machine move cost
	GRBVar[] se; // service move
	GRBVar[] abs_se;
	GRBVar[] se_cost;
	GRBEnv env;
	GRBModel model = null;

	// Map<String, GRBVar> dy;

	long load_cost;
	long balance_cost;
	long process_move_cost;
	long service_move_cost;
	long machine_move_cost;

	public GrbLPModelBuilder(Problem problem, boolean verbose,
			SolverEnvironment solverenv) {
		this.problem = problem;
		this.verbose = verbose;
		P = problem.getProcesses().size();
		M = problem.getMachines().size();
		R = problem.getResources().size();
		L = problem.getLocations().size();
		S = problem.getServices().size();
		N = problem.getNeighborhoods().size();
		BO = problem.getBalanceObjectives().size();

		x = new GRBVar[P][M];
		y = new GRBVar[S][L];
		k = new GRBVar[M][R];
		lo_cost = new GRBVar[M][R];
		a = new GRBVar[BO][M][2];
		abs = new GRBVar[BO][M];
		ba_cost = new GRBVar[BO][M];
		se = new GRBVar[S];
		abs_se = new GRBVar[S - 1];
		se_cost = new GRBVar[S - 1];

		// dy = new HashMap<String, GRBVar>();

		try {
			env = new GRBEnv("grb_build_model.log");
			env.set(GRB.IntParam.Method, 1); // dual simplex
			// env.set(GRB.DoubleParam.MIPGap, 0);
			env.set(GRB.DoubleParam.TimeLimit, 10800);
			env.set(GRB.DoubleParam.NodefileStart, 0.01);
			env.set(GRB.IntParam.Threads, 4);
			// env.set(GRB.DoubleParam.Cutoff, 727578309);
			// env.set(GRB.IntParam.OutputFlag, 0);
			model = new GRBModel(env);
		} catch (GRBException e) {
			System.out.println("GRB Error code: " + e.getErrorCode() + " -> "
					+ e.getMessage());
		}
		this.solverenv = solverenv;
	}

	public void setSolution(Solution solution) {
		setInitialSolution(solution);
	}

	public void setInitialSolution(Solution solution) {
		this.solution = solution;
	}

	public void setCurrentSolution(Solution solution) {
		translateSolutionGurobiVariableValues(solution);
	}

	private void translateSolutionGurobiVariableValues(Solution current_solution) {

		try {
			EnhancedCostEvaluator ece = new EnhancedCostEvaluator();
			ece.setSolutionPair(this.solution, current_solution);
			ece.computeCost();
			long ub = ece.getNewSolutionCost();
			model.getEnv().set(GRB.DoubleParam.Cutoff, ub + 1);

			GRBVar[] vars = new GRBVar[P];
			double[] vals = new double[P];
			for (int p = 0; p < P; p++) {
				int m = current_solution.getMachineIdForProcessId(p);
				vars[p] = x[p][m];
				vals[p] = 1.0;
			}
			model.set(GRB.DoubleAttr.Start, vars, vals);
			model.update();
		} catch (GRBException e) {
			e.printStackTrace();
		}
	}

	public void solveWithHot(Solution s) {
		createModel();
		translateSolutionGurobiVariableValues(s);
		performSolve();
	}

	public void solve() {
		createModel();
		performSolve();
	}

	private void createModel() {
		try {
			addVariables();
			addConstraints0();
			addConstraints1();
			addConstraints2();
			addConstraints3();
			addConstraints4();
			addConstraints5();
			addSoftConstraints0();
			addSoftConstraints1();
			addSoftConstraints2();
			addSoftConstraints3();
			addSoftConstraints4();
			addObjectiveUpperLimitConstraint();
			model.update();
		} catch (GRBException ex) {
			ex.printStackTrace();
		}
	}

	public void performSolve() {
		try {
			model.write("machine_reassignment.lp");
			model.optimize();
			model.terminate();
			System.out.printf("Objective value=%.0f\n",
					model.get(GRB.DoubleAttr.ObjVal));
			load_cost = computeLoadCost();
			balance_cost = computeBalanceCost();
			process_move_cost = computeProcessMoveCost();
			service_move_cost = computeServiceMoveCost();
			machine_move_cost = computeMachineMoveCost();
			if (verbose) {
				System.out
						.printf("[GRB Solution=%d] load_cost=%d balance_cost=%d process move cost=%d "
								+ "service move cost=%d machine move cost=%d\n",
								(long) Math.round(model
										.get(GRB.DoubleAttr.ObjVal)),
								load_cost, balance_cost, process_move_cost,
								service_move_cost, machine_move_cost);
				for (int m = 0; m < problem.M; m++)
					for (int r = 0; r < problem.RE; r++) {
						System.out.printf("lo_cost_m%dr%d = %.2f\n", m, r,
								lo_cost[m][r].get(GRB.DoubleAttr.X));
					}
			}
		} catch (GRBException ex) {
			ex.printStackTrace();
		}
	}

	public void solveWithFixedMachines(int nom) {
		try {
			addVariables();
			addConstraints0();
			addConstraints1();
			addConstraints2();
			addConstraints3();
			addConstraints4();
			addConstraints5();
			addSoftConstraints0();
			addSoftConstraints1();
			addSoftConstraints2();
			addSoftConstraints3();
			addSoftConstraints4();
			addFixes(nom);
			model.update();
			model.write("machine_reassignment_fixes.lp");
			model.optimize();
			model.terminate();
			load_cost = computeLoadCost();
			balance_cost = computeBalanceCost();
			process_move_cost = computeProcessMoveCost();
			service_move_cost = computeServiceMoveCost();
			machine_move_cost = computeMachineMoveCost();
			if (verbose) {
				System.out
						.printf("[GRB Solution=%d] load_cost=%d balance_cost=%d process move cost=%d "
								+ "service move cost=%d machine move cost=%d\n",
								(long) Math.round(model
										.get(GRB.DoubleAttr.ObjVal)),
								load_cost, balance_cost, process_move_cost,
								service_move_cost, machine_move_cost);
			}
		} catch (GRBException ex) {
			ex.printStackTrace();
		}
	}

	public void solveLP() {
		try {
			addVariables();
			addConstraints0();
			addConstraints1();
			addConstraints2();
			addConstraints3();
			addConstraints4();
			addConstraints5();
			addSoftConstraints0();
			addSoftConstraints1();
			addSoftConstraints2();
			addSoftConstraints3();
			addSoftConstraints4();
			makeAllVariablesContinuous();
			model.update();
			model.optimize();
			model.terminate();
			System.out.printf("Objective value=%.1f\n",
					model.get(GRB.DoubleAttr.ObjVal));
		} catch (GRBException ex) {
			ex.printStackTrace();
		}
	}

	private long computeLoadCost() throws GRBException {
		long sum = 0;
		for (int m = 0; m < M; m++) {
			long sum2 = 0;
			for (int r = 0; r < R; r++) {
				long r_value = Math.round(lo_cost[m][r].get(GRB.DoubleAttr.X));
				Resource aResource = problem.getResourceByIndex(r);
				sum2 += r_value * aResource.getWeightLoadCost();
			}
			// System.out.printf("machine:%d load_cost:%d\n", m, sum2);
			sum += sum2;
		}
		return sum;
	}

	private long computeBalanceCost() throws GRBException {
		long sum = 0;
		int bo_index = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int m = 0; m < M; m++) {
				long r_value = Math.round(ba_cost[bo_index][m]
						.get(GRB.DoubleAttr.X));
				sum += r_value * bo.weightBalanceCost;
			}
		}
		return sum;
	}

	private long computeProcessMoveCost() throws GRBException {
		return (long) Math.round(pm_cost.get(GRB.DoubleAttr.X))
				* problem.getWeightProcessMoveCost();
	}

	private long computeServiceMoveCost() throws GRBException {
		long sum = (long) Math.round(se_cost[S - 2].get(GRB.DoubleAttr.X))
				* problem.getWeightServiceMoveCost();
		return sum;
	}

	private long computeMachineMoveCost() throws GRBException {
		return (long) Math.round(mm_cost.get(GRB.DoubleAttr.X))
				* problem.getWeightMachineMoveCost();
	}

	private void addFixes(int nom) throws GRBException {
		List<Integer> aList = new ArrayList<Integer>();
		for (int i = 0; i < M; i++) {
			aList.add(i);
		}
		Random r = new Random(1234567898l);
		Collections.shuffle(aList, r);
		if (nom >= M)
			nom = M;
		for (int i = 0; i < nom; i++)
			fixMachine(aList.get(i));
	}

	private void addVariables() throws GRBException {
		for (int i = 0; i < P; i++)
			for (int j = 0; j < M; j++) {
				x[i][j] = model.addVar(0.0, 1.0, 0.0, GRB.BINARY,
						String.format("x%d_%d", i, j));
			}
		for (int s = 0; s < S; s++)
			for (int l = 0; l < L; l++) {
				y[s][l] = model.addVar(0.0, 1.0, 0.0, GRB.BINARY,
						String.format("y%d_%d", s, l));
			}
		for (int m = 0; m < M; m++) {
			for (int r = 0; r < R; r++) {
				Resource resource = problem.getResources().get(r);
				k[m][r] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
						GRB.INTEGER, String.format("k%d_%d", m, r));
				lo_cost[m][r] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						resource.getWeightLoadCost(), GRB.INTEGER,
						String.format("lc%d_%d", m, r));

			}
		}

		// MAY2012 START
		// for (int s = 0; s < S; s++) {
		// Service aService = problem.getServices().get(s);
		// for (Integer p : aService.getProcesses())
		// for (int n = 0; n < N; n++) {
		// String key = String.format("sa%d_pa%d_n%d", s, p, n);
		// dy.put(key, model.addVar(0.0, 1.0, 0.0, GRB.BINARY, key));
		// }
		// }
		// MAY2012 END

		for (int bo_index = 0; bo_index < BO; bo_index++) {
			BalanceObjective bo = problem.getBalanceObjectives().get(bo_index);
			for (int m = 0; m < M; m++) {
				abs[bo_index][m] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						0.0, GRB.CONTINUOUS,
						String.format("abs_bo%d_m%d", bo_index, m));
				ba_cost[bo_index][m] = model.addVar(0.0,
						Double.POSITIVE_INFINITY, bo.weightBalanceCost,
						GRB.CONTINUOUS,
						String.format("ba_cost_bo%d_m%d", bo_index, m));
				a[bo_index][m][0] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						0.0, GRB.INTEGER, String.format("a_bo%d_m%d_r%d",
								bo_index, m, bo.resourcea));
				a[bo_index][m][1] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						0.0, GRB.INTEGER, String.format("a_bo%d_m%d_r%d",
								bo_index, m, bo.resourceb));
			}
		}

		pm_cost = model.addVar(0.0, Double.POSITIVE_INFINITY,
				problem.getWeightProcessMoveCost(), GRB.CONTINUOUS, "pm_cost");
		mm_cost = model.addVar(0.0, Double.POSITIVE_INFINITY,
				problem.getWeightMachineMoveCost(), GRB.INTEGER, "mm_cost");
		for (int s = 0; s < S; s++) {
			se[s] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
					GRB.INTEGER, String.format("se_s%d", s));
		}
		for (int s = 0; s < S - 1; s++) {
			abs_se[s] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
					GRB.INTEGER, String.format("abs_se_s%d", s));
			if (s == S - 2)
				se_cost[s] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						problem.getWeightServiceMoveCost(), GRB.INTEGER,
						String.format("se_cost_s%d", s));
			else
				se_cost[s] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
						GRB.INTEGER, String.format("se_cost_s%d", s));
		}
		model.update();
	}

	private void makeAllVariablesContinuous() throws GRBException {
		for (int i = 0; i < P; i++)
			for (int j = 0; j < M; j++) {
				x[i][j].set(GRB.CharAttr.VType, 'C');
			}
		for (int s = 0; s < S; s++)
			for (int l = 0; l < L; l++) {
				y[s][l].set(GRB.CharAttr.VType, 'C');
			}
		for (int m = 0; m < M; m++) {
			for (int r = 0; r < R; r++) {
				k[m][r].set(GRB.CharAttr.VType, 'C');
				lo_cost[m][r].set(GRB.CharAttr.VType, 'C');
			}
		}
		for (int bo_index = 0; bo_index < BO; bo_index++) {
			for (int m = 0; m < M; m++) {
				abs[bo_index][m].set(GRB.CharAttr.VType, 'C');
				ba_cost[bo_index][m].set(GRB.CharAttr.VType, 'C');
				a[bo_index][m][0].set(GRB.CharAttr.VType, 'C');
				a[bo_index][m][1].set(GRB.CharAttr.VType, 'C');
			}
		}

		pm_cost.set(GRB.CharAttr.VType, 'C');
		mm_cost.set(GRB.CharAttr.VType, 'C');
		for (int s = 0; s < S; s++) {
			se[s].set(GRB.CharAttr.VType, 'C');
		}
		for (int s = 0; s < S - 1; s++) {
			abs_se[s].set(GRB.CharAttr.VType, 'C');
			if (s == S - 2)
				se_cost[s].set(GRB.CharAttr.VType, 'C');
			else
				se_cost[s].set(GRB.CharAttr.VType, 'C');
		}
		model.update();
	}

	private void addConstraints0() throws GRBException {
		for (int i = 0; i < P; i++) {
			GRBLinExpr expr = new GRBLinExpr();
			for (int j = 0; j < M; j++) {
				expr.addTerm(1.0, x[i][j]);
			}
			model.addConstr(expr, GRB.EQUAL, 1.0,
					String.format("assignment_constraint_p%d", i));
		}
	}

	// capacity constraints
	private void addConstraints1() throws GRBException {
		for (int m = 0; m < M; m++) {
			for (int r = 0; r < R; r++) {
				GRBLinExpr expr = new GRBLinExpr();
				for (int p = 0; p < P; p++) {
					expr.addTerm(problem.R[p][r], x[p][m]);
				}
				int capacity = problem.C[m][r];
				model.addConstr(expr, GRB.LESS_EQUAL, capacity,
						String.format("capacity_constr_m%d_r%d", m, r));
			}
		}
	}

	// conflict constraints
	private void addConstraints2() throws GRBException {
		int k = 0;
		for (Service service : problem.getServices()) {
			List<Integer> processes = service.getProcesses();
			if (processes.size() == 1)
				continue;
			else {
				for (int m = 0; m < M; m++) {
					GRBLinExpr expr = new GRBLinExpr();
					for (Integer p : processes) {
						expr.addTerm(1.0, x[p][m]);
					}
					model.addConstr(expr, GRB.LESS_EQUAL, 1.0,
							String.format("conflict_constr_s%d", k));
					k++;
				}
			}
		}
	}

	// spread constraints
	private void addConstraints3() throws GRBException {
		int k = 0;
		for (Service service : problem.getServices()) {
			if (service.getSpreadMin() == 0) {
				k++;
				continue;
			}
			GRBLinExpr expr = new GRBLinExpr();
			for (Location location : problem.getLocations()) {
				GRBLinExpr expr1 = new GRBLinExpr();
				GRBLinExpr expr2 = new GRBLinExpr();
				for (Integer m : location.getMachines()) {
					for (Integer p : service.getProcesses()) {
						expr1.addTerm(1.0, x[p][m]);
						expr2.addTerm(1.0, x[p][m]);
					}
				}
				expr1.addTerm(-1.0, y[k][location.getId()]);
				expr2.addTerm(-location.getMachines().size(),
						y[k][location.getId()]);
				model.addConstr(
						expr1,
						GRB.GREATER_EQUAL,
						0.0,
						String.format("spread_constr_s%d_l%d_a", k,
								location.getId()));
				model.addConstr(
						expr2,
						GRB.LESS_EQUAL,
						0.0,
						String.format("spread_constr_s%d_l%d_b", k,
								location.getId()));
				expr.addTerm(1.0, y[k][location.getId()]);
			}
			model.addConstr(expr, GRB.GREATER_EQUAL, service.getSpreadMin(),
					String.format("spread_constr_s%d", k));
			k++;
		}
	}

	// int BIGM = 100;

	// Dependency constraints
	// private void addConstraints4() throws GRBException {
	// int sa_index = 0;
	// for (Service servicea : problem.getServices()) {
	// if (servicea.getDependsOn().isEmpty()) {
	// sa_index++;
	// continue;
	// }
	// for (Integer pa_index : servicea.getProcesses()) {
	// GRBLinExpr expr2 = new GRBLinExpr();
	// int rhs = problem.getNeighborhoods().size() - 1;
	// for (Neighborhood neighborhood : problem.getNeighborhoods()) {
	// String key = String.format("sa%d_pa%d_n%d", sa_index,
	// pa_index, neighborhood.getId());
	// for (Integer sb_index : servicea.getDependsOn()) {
	// Service serviceb = problem.getServices().get(sb_index);
	// GRBLinExpr expr = new GRBLinExpr();
	// for (Integer m_nb : neighborhood.getMachines()) {
	// expr.addTerm(1.0, x[pa_index][m_nb]);
	// for (Integer pb_index : serviceb.getProcesses()) {
	// expr.addTerm(-1.0, x[pb_index][m_nb]);
	// }
	// }
	// expr.addTerm(-BIGM, dy.get(key));
	// model.addConstr(expr, GRB.LESS_EQUAL, 0.0, String
	// .format("depends_constr_sa%d_sb%d_p%d_n%d",
	// sa_index, sb_index, pa_index,
	// neighborhood.getId()));
	// }
	// expr2.addTerm(1.0, dy.get(key));
	// }
	// model.addConstr(expr2, GRB.EQUAL, rhs,
	// String.format("depends_constr_sa%d_binding", sa_index));
	// }
	// sa_index++;
	// }
	// }

	// Dependency constraints
	private void addConstraints4() throws GRBException {
		int sa_index = 0;
		for (Service servicea : problem.getServices()) {
			if (servicea.getDependsOn().isEmpty()) {
				sa_index++;
				continue;
			}
			for (Integer sb_index : servicea.getDependsOn()) {
				Service serviceb = problem.getServices().get(sb_index);
				for (Integer pa_index : servicea.getProcesses()) {
					for (Neighborhood neighborhood : problem.getNeighborhoods()) {
						GRBLinExpr expr = new GRBLinExpr();
						for (Integer m_nb : neighborhood.getMachines()) {
							expr.addTerm(1.0, x[pa_index][m_nb]);
							for (Integer pb_index : serviceb.getProcesses()) {
								expr.addTerm(-1.0, x[pb_index][m_nb]);
							}
						}
						model.addConstr(expr, GRB.LESS_EQUAL, 0.0, String
								.format("depends_constr_sa%d_sb%d_p%d_n%d",
										sa_index, sb_index, pa_index,
										neighborhood.getId()));
					}
				}
			}
			sa_index++;
		}
	}

	// Transient constraints
	private void addConstraints5() throws GRBException {
		for (int r = 0; r < R; r++) {
			Resource resource = problem.getResourceByIndex(r);
			if (!resource.isTransientUsage())
				continue;
			for (Integer m : solution.getMachinesInSolution()) {
				GRBLinExpr expr = new GRBLinExpr();
				int capacity = problem.C[m][r];
				for (int p = 0; p < P; p++) {
					if (solution.getMachineIdForProcessId(p) == m) {
						// process already exists in solution in that machine
						capacity -= problem.R[p][r];
					} else {
						expr.addTerm(problem.R[p][r], x[p][m]);
					}
				}

				model.addConstr(expr, GRB.LESS_EQUAL, capacity,
						String.format("transient_constr_m%d_r%d", m, r));
			}
		}
	}

	// load cost
	private void addSoftConstraints0() throws GRBException {
		for (int r = 0; r < R; r++) {
			for (int m = 0; m < M; m++) {
				GRBLinExpr expr1 = new GRBLinExpr();
				GRBLinExpr expr2 = new GRBLinExpr();
				GRBLinExpr expr3 = new GRBLinExpr();
				for (int p = 0; p < P; p++) {
					expr1.addTerm(problem.R[p][r] / 2.0, x[p][m]);
					expr2.addTerm(-problem.R[p][r], x[p][m]);
					expr3.addTerm(problem.R[p][r], x[p][m]);
				}
				expr1.addTerm(0.5, k[m][r]);
				expr1.addTerm(-1.0, lo_cost[m][r]);
				int safety_capacity = problem.getMachines().get(m)
						.getSafetyCapacityFor("r" + r);
				model.addConstr(expr1, GRB.EQUAL, safety_capacity,
						String.format("load_cost_constr_m%d_r%d_a", m, r));

				expr2.addTerm(1.0, k[m][r]);
				model.addConstr(expr2, GRB.GREATER_EQUAL, 0.0,
						String.format("load_cost_constr_m%d_r%d_b", m, r));

				expr3.addTerm(1.0, k[m][r]);
				model.addConstr(expr3, GRB.GREATER_EQUAL, 0.0,
						String.format("load_cost_constr_m%d_r%d_c", m, r));
			}
		}
	}

	// balance cost
	private void addSoftConstraints1() throws GRBException {
		int bo_index = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int m = 0; m < M; m++) {
				GRBLinExpr expr1a = new GRBLinExpr();
				GRBLinExpr expr1b = new GRBLinExpr();
				for (int p = 0; p < P; p++) {
					expr1a.addTerm(problem.R[p][bo.resourcea], x[p][m]);
					expr1b.addTerm(problem.R[p][bo.resourceb], x[p][m]);
				}
				expr1a.addTerm(1.0, a[bo_index][m][0]);
				expr1b.addTerm(1.0, a[bo_index][m][1]);

				int capacity_a = problem.getMachines().get(m)
						.getCapacityFor("r" + bo.resourcea);
				int capacity_b = problem.getMachines().get(m)
						.getCapacityFor("r" + bo.resourceb);
				model.addConstr(expr1a, GRB.EQUAL, capacity_a, String.format(
						"balance_cost_constr_bo%d_m%d_r%d_a%d_%d", bo_index, m,
						bo.resourcea, m, bo.resourcea));
				model.addConstr(expr1b, GRB.EQUAL, capacity_b, String.format(
						"balance_cost_constr_bo%d_m%d_r%d_a%d_%d", bo_index, m,
						bo.resourceb, m, bo.resourceb));
			}
			for (int m = 0; m < M; m++) {
				GRBLinExpr expr2 = new GRBLinExpr();
				GRBLinExpr expr3 = new GRBLinExpr();
				GRBLinExpr expr4 = new GRBLinExpr();
				expr2.addTerm(bo.target / 2.0, a[bo_index][m][0]);
				expr2.addTerm(-0.5, a[bo_index][m][1]);
				expr2.addTerm(0.5, abs[bo_index][m]);
				expr2.addTerm(-1.0, ba_cost[bo_index][m]);
				model.addConstr(expr2, GRB.EQUAL, 0.0, String.format(
						"balance_cost_constr_bo%d_m%d_a", bo_index, m));

				expr3.addTerm(1.0, abs[bo_index][m]);
				expr3.addTerm(bo.target, a[bo_index][m][0]);
				expr3.addTerm(-1.0, a[bo_index][m][1]);
				model.addConstr(expr3, GRB.GREATER_EQUAL, 0.0, String.format(
						"balance_cost_constr_bo%d_m%d_b", bo_index, m));

				expr4.addTerm(1.0, abs[bo_index][m]);
				expr4.addTerm(-bo.target, a[bo_index][m][0]);
				expr4.addTerm(1.0, a[bo_index][m][1]);
				model.addConstr(expr4, GRB.GREATER_EQUAL, 0.0, String.format(
						"balance_cost_constr_bo%d_m%d_c", bo_index, m));
			}
			bo_index++;
		}
	}

	// process move cost
	private void addSoftConstraints2() throws GRBException {
		GRBLinExpr expr = new GRBLinExpr();
		int sum = 0;
		for (int p = 0; p < P; p++) {
			int pmc = problem.getProcesses().get(p).getProcessMoveCost();
			for (int m = 0; m < M; m++) {
				if (solution.getMachineIdForProcessId(p) == m) {
					sum += pmc;
					expr.addTerm(-pmc, x[p][m]);
				} else {
					expr.addTerm(pmc, x[p][m]);
				}
			}
		}
		expr.addTerm(-2.0, pm_cost);
		model.addConstr(expr, GRB.EQUAL, -sum, "process_move_cost");
	}

	// service move cost
	private void addSoftConstraints3() throws GRBException {
		if (S <= 1)
			return;
		for (int s = 0; s < S; s++) {
			GRBLinExpr expr = new GRBLinExpr();
			Service service = problem.getServices().get(s);
			for (Integer p : service.getProcesses()) {
				for (int m = 0; m < M; m++) {
					if (solution.getMachineIdForProcessId(p) == m) {
						expr.addTerm(-1.0, x[p][m]);
					} else {
						expr.addTerm(1.0, x[p][m]);
					}
				}
			}
			expr.addTerm(-2.0, se[s]);
			model.addConstr(expr, GRB.EQUAL, -service.getProcesses().size(),
					String.format("service_move_cost_constr_s%d", s));
		}

		GRBLinExpr expr1 = new GRBLinExpr();
		expr1.addTerm(0.5, se[0]);
		expr1.addTerm(0.5, se[1]);
		expr1.addTerm(0.5, abs_se[0]);
		expr1.addTerm(-1.0, se_cost[0]);
		model.addConstr(expr1, GRB.EQUAL, 0,
				String.format("service_move_cost_constr_max_s0_a"));

		GRBLinExpr expr2 = new GRBLinExpr();
		expr2.addTerm(1.0, abs_se[0]);
		expr2.addTerm(1.0, se[0]);
		expr2.addTerm(-1.0, se[1]);
		model.addConstr(expr2, GRB.GREATER_EQUAL, 0,
				String.format("service_move_cost_constr_max_s0_b"));

		GRBLinExpr expr3 = new GRBLinExpr();
		expr3.addTerm(1.0, abs_se[0]);
		expr3.addTerm(-1.0, se[0]);
		expr3.addTerm(1.0, se[1]);
		model.addConstr(expr3, GRB.GREATER_EQUAL, 0,
				String.format("service_move_cost_constr_max_s0_c"));

		for (int s = 2; s < S; s++) {
			GRBLinExpr expr4 = new GRBLinExpr();
			expr4.addTerm(0.5, se_cost[s - 2]);
			expr4.addTerm(0.5, se[s]);
			expr4.addTerm(0.5, abs_se[s - 1]);
			expr4.addTerm(-1.0, se_cost[s - 1]);
			model.addConstr(expr4, GRB.EQUAL, 0,
					String.format("service_move_cost_constr_max_s%d_a", s));

			GRBLinExpr expr5 = new GRBLinExpr();
			expr5.addTerm(1.0, abs_se[s - 1]);
			expr5.addTerm(1.0, se_cost[s - 2]);
			expr5.addTerm(-1.0, se[s]);
			model.addConstr(expr5, GRB.GREATER_EQUAL, 0,
					String.format("service_move_cost_constr_max_s%d_b", s));

			GRBLinExpr expr6 = new GRBLinExpr();
			expr6.addTerm(1.0, abs_se[s - 1]);
			expr6.addTerm(-1.0, se_cost[s - 2]);
			expr6.addTerm(1.0, se[s]);
			model.addConstr(expr6, GRB.GREATER_EQUAL, 0,
					String.format("service_move_cost_constr_max_s%d_c", s));
		}
	}

	// machine move cost
	private void addSoftConstraints4() throws GRBException {
		GRBLinExpr expr = new GRBLinExpr();
		for (int p = 0; p < P; p++) {
			int msource = solution.getMachineIdForProcessId(p);
			Machine aMachine = problem.getMachines().get(msource);
			for (int mdest = 0; mdest < M; mdest++) {
				if (msource != mdest) {
					expr.addTerm(aMachine.getMachineMoveCost(mdest),
							x[p][mdest]);
				}
			}
		}
		expr.addTerm(-1.0, mm_cost);
		model.addConstr(expr, GRB.EQUAL, 0, "machine_move_cost");
	}

	private void addObjectiveUpperLimitConstraint() throws GRBException {
		GRBLinExpr expr = new GRBLinExpr();
		for (int m = 0; m < M; m++) {
			for (int r = 0; r < R; r++) {
				Resource resource = problem.getResources().get(r);
				expr.addTerm(resource.getWeightLoadCost(), lo_cost[m][r]);
			}
		}
		for (int bo_index = 0; bo_index < BO; bo_index++) {
			BalanceObjective bo = problem.getBalanceObjectives().get(bo_index);
			for (int m = 0; m < M; m++) {
				expr.addTerm(bo.weightBalanceCost, ba_cost[bo_index][m]);
			}
		}
		expr.addTerm(problem.getWeightProcessMoveCost(), pm_cost);
		expr.addTerm(problem.getWeightMachineMoveCost(), mm_cost);
		expr.addTerm(problem.getWeightServiceMoveCost(), se_cost[S - 2]);
		solverenv.getFullValidator().computeCost();
		model.addConstr(expr, GRB.LESS_EQUAL, solverenv.getFullValidator()
				.getNewSolutionCost(), "current_objective");
	}

	public void fixMachine(int machine_id) throws GRBException {
		List<Integer> processes = solution.getProcessesForMachine(machine_id);
		for (Integer process_id : processes) {
			GRBLinExpr expr = new GRBLinExpr();
			expr.addTerm(1.0, x[process_id][machine_id]);
			model.addConstr(expr, GRB.EQUAL, 1,
					String.format("fix_m%d_p%d", machine_id, process_id));
		}
	}

	public Solution saveToSolution() {
		Solution solution = new Solution(problem);
		for (int p = 0; p < P; p++) {
			for (int m = 0; m < M; m++) {
				try {
					double x_value = x[p][m].get(GRB.DoubleAttr.X);
					if (x_value > 0.9999) {
						solution.assignProcessToMachine(p, m);
					}
				} catch (GRBException e) {
					e.printStackTrace();
				}
			}
		}
		return solution;
	}

	public Solution getHeuristicSolution() {
		SolverEnvironment env = new SolverEnvironment(problem,
				this.solution.copy(), solution);
		IncrementalCostCalculator cc = env.getCostCalculator();
		IncrementalValidator iv = env.getValidator();

		cc.computeCost();

		Invoker inv = new Invoker();

		for (int p = 0; p < P; p++) {
			double max = 0.0;
			int max_machine = -1;
			for (int m = 0; m < M; m++) {
				try {
					double x_value = x[p][m].get(GRB.DoubleAttr.X);
					// if
					// (iv.isProbablyFeasibleNoSchedule(p,solution.getMachineIdForProcessId(p),
					// m)){
					if (x_value > max) {
						max = x_value;
						max_machine = m;
						if (x_value > 0.9999) {
							break;
						}
					}
					// }
				} catch (GRBException e) {
					e.printStackTrace();
				}
			}

			boolean shouldSearch = true;
			IncrementalMoveProcessToMachineCommand cmd = new IncrementalMoveProcessToMachineCommand(
					iv, cc);
			cmd.setP_id(p);

			if (max_machine != -1) {
				if (iv.isProbablyFeasibleNoSchedule(p,
						solution.getMachineIdForProcessId(p), max_machine)) {
					cmd.setM_id(max_machine);
					inv.addCommand(cmd);
					inv.executeLastCommand();
					if (!iv.isFinallyFeasible(p,
							solution.getMachineIdForProcessId(p), max_machine)) {
						inv.undoLastCommand();
					} else {
						shouldSearch = false;
						inv.clearCommands();
					}
				}
			}

			if (shouldSearch) {// Find a random feasible solution
				for (int _m1 = max_machine + 1; _m1 < M + max_machine; _m1++) {
					int m1 = _m1 % M;
					if (iv.isProbablyFeasibleNoSchedule(p,
							solution.getMachineIdForProcessId(p), m1)) {
						cmd.setM_id(m1);
						inv.addCommand(cmd);
						inv.executeLastCommand();

						if (!iv.isFinallyFeasible(p,
								this.solution.getMachineIdForProcessId(p), m1)) {
							inv.undoLastCommand();
						} else {
							inv.clearCommands();
							break;
						}
					}
				}
			}

		}

		return solution;
	}
}